1 /*
2 * Copyright (c) 2005, 2009, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package sun.net.www.protocol.http;
27
28 import java.net.URL;
29 import java.io.IOException;
30 import java.net.Authenticator.RequestorType;
31 import java.util.HashMap;
32 import sun.net.www.HeaderParser;
33 import sun.misc.BASE64Decoder;
34 import sun.misc.BASE64Encoder;
35 import static sun.net.www.protocol.http.AuthScheme.NEGOTIATE;
36 import static sun.net.www.protocol.http.AuthScheme.KERBEROS;
37
38 /**
39 * NegotiateAuthentication:
40 *
41 * @author weijun.wang@sun.com
42 * @since 1.6
43 */
44
45 class NegotiateAuthentication extends AuthenticationInfo {
46
47 private static final long serialVersionUID = 100L;
48
49 final private HttpCallerInfo hci;
50
51 // These maps are used to manage the GSS availability for diffrent
52 // hosts. The key for both maps is the host name.
53 // <code>supported</code> is set when isSupported is checked,
54 // if it's true, a cached Negotiator is put into <code>cache</code>.
55 // the cache can be used only once, so after the first use, it's cleaned.
56 static HashMap <String, Boolean> supported = null;
57 static HashMap <String, Negotiator> cache = null;
58
59 // The HTTP Negotiate Helper
60 private Negotiator negotiator = null;
61
62 /**
63 * Constructor used for both WWW and proxy entries.
64 * @param hci a schemed object.
65 */
66 public NegotiateAuthentication(HttpCallerInfo hci) {
67 super(RequestorType.PROXY==hci.authType ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
68 hci.scheme.equalsIgnoreCase("Negotiate") ? NEGOTIATE : KERBEROS,
69 hci.url,
70 "");
71 this.hci = hci;
72 }
73
74 /**
75 * @return true if this authentication supports preemptive authorization
76 */
77 @Override
78 public boolean supportsPreemptiveAuthorization() {
79 return false;
80 }
81
82 /**
83 * Find out if the HttpCallerInfo supports Negotiate protocol. In order to
84 * find out yes or no, an initialization of a Negotiator object against it
85 * is tried. The generated object will be cached under the name of ths
86 * hostname at a success try.<br>
87 *
88 * If this method is called for the second time on an HttpCallerInfo with
89 * the same hostname, the answer is retrieved from cache.
90 *
91 * @return true if supported
92 */
93 synchronized public static boolean isSupported(HttpCallerInfo hci) {
94 if (supported == null) {
95 supported = new HashMap <String, Boolean>();
96 cache = new HashMap <String, Negotiator>();
97 }
98 String hostname = hci.host;
99 hostname = hostname.toLowerCase();
100 if (supported.containsKey(hostname)) {
101 return supported.get(hostname);
102 }
103
104 Negotiator neg = Negotiator.getNegotiator(hci);
105 if (neg != null) {
106 supported.put(hostname, true);
107 // the only place cache.put is called. here we can make sure
108 // the object is valid and the oneToken inside is not null
109 cache.put(hostname, neg);
110 return true;
111 } else {
112 supported.put(hostname, false);
113 return false;
114 }
115 }
116
117 /**
118 * Not supported. Must use the setHeaders() method
119 */
120 @Override
121 public String getHeaderValue(URL url, String method) {
122 throw new RuntimeException ("getHeaderValue not supported");
123 }
124
125 /**
126 * Check if the header indicates that the current auth. parameters are stale.
127 * If so, then replace the relevant field with the new value
128 * and return true. Otherwise return false.
129 * returning true means the request can be retried with the same userid/password
130 * returning false means we have to go back to the user to ask for a new
131 * username password.
132 */
133 @Override
134 public boolean isAuthorizationStale (String header) {
135 return false; /* should not be called for Negotiate */
136 }
137
138 /**
139 * Set header(s) on the given connection.
140 * @param conn The connection to apply the header(s) to
141 * @param p A source of header values for this connection, not used because
142 * HeaderParser converts the fields to lower case, use raw instead
143 * @param raw The raw header field.
144 * @return true if all goes well, false if no headers were set.
145 */
146 @Override
147 public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
148
149 try {
150 String response;
151 byte[] incoming = null;
152 String[] parts = raw.split("\\s+");
153 if (parts.length > 1) {
154 incoming = new BASE64Decoder().decodeBuffer(parts[1]);
155 }
156 response = hci.scheme + " " + new B64Encoder().encode(
157 incoming==null?firstToken():nextToken(incoming));
158
159 conn.setAuthenticationProperty(getHeaderName(), response);
160 return true;
161 } catch (IOException e) {
162 return false;
163 }
164 }
165
166 /**
167 * return the first token.
168 * @returns the token
169 * @throws IOException if <code>Negotiator.getNegotiator()</code> or
170 * <code>Negotiator.firstToken()</code> failed.
171 */
172 private byte[] firstToken() throws IOException {
173 negotiator = null;
174 if (cache != null) {
175 synchronized(cache) {
176 negotiator = cache.get(getHost());
177 if (negotiator != null) {
178 cache.remove(getHost()); // so that it is only used once
179 }
180 }
181 }
182 if (negotiator == null) {
183 negotiator = Negotiator.getNegotiator(hci);
184 if (negotiator == null) {
185 IOException ioe = new IOException("Cannot initialize Negotiator");
186 throw ioe;
187 }
188 }
189
190 return negotiator.firstToken();
191 }
192
193 /**
194 * return more tokens
195 * @param token the token to be fed into <code>negotiator.nextToken()</code>
196 * @returns the token
197 * @throws IOException if <code>negotiator.nextToken()</code> throws Exception.
198 * May happen if the input token is invalid.
199 */
200 private byte[] nextToken(byte[] token) throws IOException {
201 return negotiator.nextToken(token);
202 }
203
204 class B64Encoder extends BASE64Encoder {
205 protected int bytesPerLine () {
206 return 100000; // as big as it can be, maybe INT_MAX
207 }
208 }
209
210 // MS will send a final WWW-Authenticate even if the status is already
211 // 200 OK. The token can be fed into initSecContext() again to determine
212 // if the server can be trusted. This is not the same concept as Digest's
213 // Authentication-Info header.
214 //
215 // Currently we ignore this header.
216
217 }